
# Copyright 2009 Mithun Chaubey 
# 
# Licensed under the Apache License, Version 2.0 (the "License"); you 
# may not use this file except in compliance with the License. You may 
# obtain a copy of the License at 
# 
#     http://www.apache.org/licenses/LICENSE-2.0
#     
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing  permissions and
# limitations under the License.

''' Module for wiki-ish interface for Living History '''

from google.appengine.ext import db
from google.appengine.ext.db import polymodel

import lh_user

class entry(polymodel.PolyModel):
    ''' A wiki node (analogous to wiki page) for any group of entries '''
    date = db.DateTimeProperty(auto_now_add = True)
    creator = db.ReferenceProperty(lh_user.lh_user)
    
    def put(self):
        '''
        Add entry to datastore
        @precondition: The entry's creator is specified
        '''
        if not self.creator:
            raise ValueError, 'Wiki entry creator not defined'
        super(entry, self).put()
        
    def delete(self):
        ''' Delete entry from datastore (deletes all associated revisions) '''
        revs = self.all_revisions()
        for rev in revs:
            rev.delete()
            
        super(entry, self).delete()
        
    def new_revision(self, new_rev, comment, user):
        '''
        Store a new revision for this entry
        @precondition: This entry must be saved in the datastore
        
        @param rev: A new revision to the entry
        @param comment: The revision comment
        @param user: The user who authored the revision
        
        @type revision: revision
        @type comment: String
        @type user: lh_user
        '''
        
        old_rev = self.latest_revision()
        
        # Add revision properties to rev object and put in datastore
        new_rev.entry = self
        new_rev.comment = comment
        new_rev.creator = user
        new_rev.put()
        
        if old_rev:
            # Copy links to last revision to the new one
            revs_from = old_rev.revisions_linked_from()
            for rev_from in revs_from:
                rev_from.link_to(new_rev)
            
            # Follow links from the last revision, and add a new revision to their entries
            revs_to = old_rev.revisions_linked_to()
            
            for rev_to in revs_to:
                
                # The entry to which the revision that was linked to belongs
                entry = rev_to.entry
                
                # Copy last revision from, add to entry as new revision
                rev_to_new = entry.latest_revision().copy()
                entry.new_revision(rev_to_new, comment, user)
                
                # Now that new entry revision is linked from old_rev, unlink it
                old_rev.unlink_to(rev_to_new)
                
                new_rev.link_to(rev_to_new)
            
        return new_rev
    
    def latest_revision(self):
        '''
        The most recent of all revisions connected to this entry
        @rtype: revision
        '''
        revs = db.GqlQuery("SELECT * FROM revision WHERE entry = :1 ORDER BY date DESC LIMIT 1", self)
        return revs.get()
    
    def all_revisions(self):
        '''
        All revisions of this entry
        @return: List of revisions by date descending
        @rtype: list[revision]
        '''
        
        revs = db.GqlQuery("SELECT * FROM revision WHERE entry = :1 ORDER BY date DESC", self)
        return revs

class revision(polymodel.PolyModel):
    ''' A revision of an entry in the wiki. Derived classes must include a "copy" method '''
    date = db.DateTimeProperty(auto_now_add = True)
    creator = db.ReferenceProperty(lh_user.lh_user)
    entry = db.ReferenceProperty(entry)
    comment = db.StringProperty()
    
    def delete(self):
        ''' Delete revision from datastore. Deletes all links '''
        try:
            revs_linked_to = self.revisions_linked_to()
            revs_linked_from = self.revisions_linked_from()
            for rev_linked_to in revs_linked_to:
                self.unlink_to(rev_linked_to)
            for rev_linked_from in revs_linked_from:
                self.unlink_from(rev_linked_from)
        except: raise
        
        entry = self.entry
        super(revision, self).delete()
        if entry.latest_revision() is None:
            entry.delete()
    
    def put(self):
        '''
        Store revision in datastore
        @precondition: Revision is associated with a wiki entry
        @precondition: Revision's creator is defined
        '''
        if not self.entry:
            raise ValueError, 'Revision entry not defined'
        if not self.creator:
            raise ValueError, 'Revision creator not defined'
        super(revision, self).put()
        
    def copy(self):
        ''' Raises an error if derived class does not include "copy" medthod '''
        raise StandardError, 'Error: subclass does not have copy() method'
    
    def revisions_linked_to(self, type = None):
        '''
        A list of revisions linked to from this revision
        @param type: An optional type to filter revisions by
        @type type: object
        @rtype: list[revision]
        '''
        links = db.GqlQuery("SELECT * FROM revision_link WHERE revision_from = :1", self)
        
        list = []
        if type:
            for link in links:
                if isinstance(link.revision_to, type):
                    list.append(link.revision_to)
        else:
            for link in links:
                list.append(link.revision_to)
                
        return list
    
    def revisions_linked_from(self, type = None):
        '''
        A list of revisions linked to this revision
        @param type: An optional type to filter revisions by
        @type type: object
        @rtype: list[revision]
        '''
        links = db.GqlQuery("SELECT * FROM revision_link WHERE revision_to = :1", self)
        
        list = []
        if type:
            for link in links:
                if isinstance(link.revision_from, type):
                    list.append(link.revision_from)
        else:
            for link in links:
                list.append(link.revision_from)
                
        return list
    
    def is_linked_to(self, rev):
        '''
        Returns true if revision is linked to rev
        @param rev: Revision to check against
        @type rev: event.revision
        @rtype: Boolean
        '''
        
        links = db.GqlQuery("SELECT * FROM revision_link WHERE revision_from = :1 AND revision_to = :2", self, rev)
        link = links.get()
        if link:
            return True
        else:
            return False
    
    def link_to(self, rev):
        '''
        Link to another revision
        @param rev: Revision to link to
        @type rev: revision
        '''
        
        link = revision_link()
        link.revision_from = self
        link.revision_to = rev
        link.put()
    
    def unlink_to(self, rev):
        '''
        Unlink link to another revision
        @param rev: Revision to unlink from
        @type rev: revision
        '''
        links = db.GqlQuery("SELECT * FROM revision_link WHERE revision_from = :1 AND revision_to = :2", self, rev)
        for link in links:
            link.delete()
            
    def unlink_from(self, rev):
        '''
        Unlink link from another revision
        @param rev: Revision from which this function unlinks
        @type rev: revision
        '''
        links = db.GqlQuery("SELECT * FROM revision_link WHERE revision_to = :1 AND revision_from = :2", self, rev)
        for link in links:
            link.delete()
        
        
        
class revision_link(db.Model):
    ''' A class to link entries in a many-to-many model '''
    revision_from = db.ReferenceProperty(revision, collection_name = 'revision_from_set')
    revision_to = db.ReferenceProperty(revision, collection_name = 'revision_to_set')
    
    def put(self):
        '''
        Store link in datastore
        @precondition: Links to and from are defined
        '''
        if not self.revision_from:
            raise ValueError, 'Link not linked from revision'
        if not self.revision_to:
            raise ValueError, 'Link not linked to revision'
        
        super(revision_link, self).put()